/*******************************************************************************
 * Copyhacked (H) 2012-2025.
 * This program and the accompanying materials
 * are made available under no term at all, use it like
 * you want, but share and discuss it
 * every time possible with every body.
 * 
 * Contributors:
 *      ron190 at ymail dot com - initial implementation
 ******************************************************************************/
package com.jsql.view.swing.manager;

import com.jsql.model.accessible.ExploitMode;
import com.jsql.model.exception.JSqlException;
import com.jsql.model.injection.vendor.model.Vendor;
import com.jsql.util.I18nUtil;
import com.jsql.util.LogLevelUtil;
import com.jsql.view.swing.manager.util.*;
import com.jsql.view.swing.text.JPasswordFieldPlaceholder;
import com.jsql.view.swing.text.JPopupTextField;
import com.jsql.view.swing.text.JTextFieldPlaceholder;
import com.jsql.view.swing.text.JToolTipI18n;
import com.jsql.view.swing.util.I18nViewUtil;
import com.jsql.view.swing.util.MediatorHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Manager for uploading PHP SQL shell to the host and send queries.
 */
public class ManagerExploit extends AbstractManagerList {

    private static final Logger LOGGER = LogManager.getRootLogger();
    public static final String SHELL_URL_TOOLTIP = "SHELL_URL_TOOLTIP";

    private final AtomicReference<JTextField> username = new AtomicReference<>();
    private final AtomicReference<JTextField> password = new AtomicReference<>();
    private final AtomicReference<JTextField> netshare = new AtomicReference<>();
    protected final JTextField textfieldUrlShell;

    public static final String EXPLOIT_UDF = "EXPLOIT_UDF";
    public static final String EXPLOIT_WEB = "EXPLOIT_WEB";
    public static final String EXPLOIT_SQL = "EXPLOIT_SQL";
    public static final String EXPLOIT_UPLOAD = "EXPLOIT_UPLOAD";

    private final JComboBox<Object> comboBoxExploitTypes = new JComboBox<>(new Object[]{
        new ModelItemType(ManagerExploit.EXPLOIT_UDF, "EXPLOIT_UDF_TOOLTIP"),
        ComboBoxMethodRenderer.SEPARATOR,
        new ModelItemType(ManagerExploit.EXPLOIT_WEB, "EXPLOIT_WEB_TOOLTIP"),
        new ModelItemType(ManagerExploit.EXPLOIT_SQL, "EXPLOIT_SQL_TOOLTIP"),
        new ModelItemType(ManagerExploit.EXPLOIT_UPLOAD, "EXPLOIT_UPLOAD_TOOLTIP"),
    });

    private final JComboBox<Object> comboBoxExploitModes = new JComboBox<>(new Object[]{
        ExploitMode.AUTO,
        ComboBoxMethodRenderer.SEPARATOR,
        ExploitMode.QUERY_BODY,
        ExploitMode.TEMP_TABLE,
        ComboBoxMethodRenderer.SEPARATOR,
        ExploitMode.NETSHARE
    });

    public ManagerExploit() {
        super("swing/list/payload.txt");

        var tooltipShellUrl = new AtomicReference<>(new JToolTipI18n(I18nUtil.valueByKey(ManagerExploit.SHELL_URL_TOOLTIP)));
        var placeholderResult = new JTextFieldPlaceholder(I18nUtil.valueByKey("SHELL_URL_LABEL")) {
            @Override
            public JToolTip createToolTip() {
                return tooltipShellUrl.get();
            }
        };
        this.textfieldUrlShell = new JPopupTextField(placeholderResult).getProxy();
        I18nViewUtil.addComponentForKey("SHELL_URL_LABEL", this.textfieldUrlShell);
        I18nViewUtil.addComponentForKey(ManagerExploit.SHELL_URL_TOOLTIP, tooltipShellUrl.get());
        this.textfieldUrlShell.setToolTipText(I18nUtil.valueByKey(ManagerExploit.SHELL_URL_TOOLTIP));

        this.buildRunButton("SHELL_RUN_BUTTON_LABEL", "SHELL_RUN_BUTTON_TOOLTIP");
        this.run.setEnabled(false);
        this.buildPrivilege();

        var southPanel = new JPanel();
        southPanel.setLayout(new BoxLayout(southPanel, BoxLayout.Y_AXIS));
        southPanel.add(this.textfieldUrlShell);
        southPanel.add(this.lastLine);
        this.add(southPanel, BorderLayout.SOUTH);

        var userPassPanel = new JPanel();
        var groupLayout = new GroupLayout(userPassPanel);
        userPassPanel.setLayout(groupLayout);

        this.run.addActionListener(new ActionExploit(this.comboBoxExploitTypes));

        Arrays.asList(
            new ModelExploit(this.netshare, "EXPLOIT_NETSHARE_LABEL", "EXPLOIT_NETSHARE_TOOLTIP"),
            new ModelExploit(this.username, "SQL_SHELL_USERNAME_LABEL", "SQL_SHELL_USERNAME_TOOLTIP"),
            new ModelExploit(this.password, "SQL_SHELL_PASSWORD_LABEL", "SQL_SHELL_PASSWORD_TOOLTIP", true)
        ).forEach(model -> {
            var tooltip = new AtomicReference<>(new JToolTipI18n(I18nUtil.valueByKey(model.tooltipI18n)));
            if (model.isPassword) {
                model.textfield.set(new JPopupTextField(new JPasswordFieldPlaceholder(I18nUtil.valueByKey(model.labelI18n)) {
                    @Override
                    public JToolTip createToolTip() {
                        return tooltip.get();
                    }
                }).getProxy());
            } else {
                model.textfield.set(new JPopupTextField(new JTextFieldPlaceholder(I18nUtil.valueByKey(model.labelI18n)) {
                    @Override
                    public JToolTip createToolTip() {
                        return tooltip.get();
                    }
                }).getProxy());
            }
            I18nViewUtil.addComponentForKey(model.labelI18n, model.textfield.get());
            I18nViewUtil.addComponentForKey(model.tooltipI18n, tooltip.get());
            model.textfield.get().setToolTipText(I18nUtil.valueByKey(model.tooltipI18n));
        });

        Arrays.asList(this.username.get(), this.password.get(), this.scrollListPaths, this.textfieldUrlShell, this.netshare.get())
        .forEach(component -> component.setVisible(false));

        this.comboBoxExploitTypes.setRenderer(new ComboBoxTypeRenderer());
        this.comboBoxExploitTypes.addActionListener(new SeparatorListener(this.comboBoxExploitTypes));
        this.comboBoxExploitTypes.addItemListener(this.getTypesItemListener());
        this.comboBoxExploitModes.addItemListener(this.getModesItemListener());

        this.comboBoxExploitModes.setRenderer(new ComboBoxMethodRenderer());
        this.comboBoxExploitModes.addActionListener(new SeparatorListener(this.comboBoxExploitModes));
        var labelUsing = new JLabel("via");
        labelUsing.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
        groupLayout.setHorizontalGroup(
            groupLayout
            .createParallelGroup()
            .addGroup(
                groupLayout
                .createSequentialGroup()
                .addComponent(this.comboBoxExploitTypes)
                .addComponent(labelUsing, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
                .addComponent(this.comboBoxExploitModes, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
            )
            .addGroup(
                groupLayout.createParallelGroup()
                .addComponent(this.netshare.get())
                .addComponent(this.username.get())
                .addComponent(this.password.get())
            )
        );

        groupLayout.setVerticalGroup(
            groupLayout
            .createSequentialGroup()
            .addGroup(
                groupLayout
                .createParallelGroup(GroupLayout.Alignment.BASELINE)
                .addComponent(this.comboBoxExploitTypes)
                .addComponent(labelUsing)
                .addComponent(this.comboBoxExploitModes)
            )
            .addGroup(
                groupLayout
                .createParallelGroup()
                .addComponent(this.netshare.get())
            )
            .addGroup(
                groupLayout
                .createParallelGroup()
                .addComponent(this.username.get())
            )
            .addGroup(
                groupLayout
                .createParallelGroup()
                .addComponent(this.password.get())
            )
        );
        
        this.add(userPassPanel, BorderLayout.NORTH);
    }

    private ItemListener getModesItemListener() {
        return itemEvent -> {
            if (itemEvent.getStateChange() == ItemEvent.SELECTED && itemEvent.getItem() instanceof ExploitMode) {
                ExploitMode selectedItem = (ExploitMode) itemEvent.getItem();
                this.netshare.get().setVisible(false);
                if (selectedItem.equals(ExploitMode.NETSHARE)) {
                    this.netshare.get().setVisible(true);
                }
                this.updateUI();  // required to adapt panel
            }
        };
    }

    private ItemListener getTypesItemListener() {
        return itemEvent -> {
            if (itemEvent.getStateChange() != ItemEvent.SELECTED || itemEvent.getItem() == ComboBoxMethodRenderer.SEPARATOR) {
                return;
            }
            Arrays.asList(
                this.username.get(), this.password.get(), this.scrollListPaths, this.textfieldUrlShell
            ).forEach(component -> component.setVisible(false));
            ModelItemType selectedItem = (ModelItemType) itemEvent.getItem();
            if (!ManagerExploit.EXPLOIT_UDF.equals(selectedItem.getKeyLabel())) {
                this.scrollListPaths.setVisible(true);
                this.textfieldUrlShell.setVisible(true);
                if (ManagerExploit.EXPLOIT_SQL.equals(selectedItem.getKeyLabel())) {
                    this.username.get().setVisible(true);
                    this.password.get().setVisible(true);
                }
            }
            this.updateUI();  // required to adapt panel
        };
    }

    protected class ActionExploit implements ActionListener {
        private final JComboBox<Object> comboBoxExploitTypes;

        public ActionExploit(JComboBox<Object> comboBoxExploitTypes) {
            this.comboBoxExploitTypes = comboBoxExploitTypes;
        }

        @Override
        public void actionPerformed(ActionEvent evt) {
            var modelSelectItem = (ModelItemType) this.comboBoxExploitTypes.getSelectedItem();
            var labelSelectItem = Objects.requireNonNull(modelSelectItem).getKeyLabel();
            if (!ManagerExploit.isValid(labelSelectItem)) {
                LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Currently unsupported for [{}], contribute and share exploit method on GitHub to improve the app", MediatorHelper.model().getMediatorVendor().getVendor());
                return;
            }
            if (ManagerExploit.EXPLOIT_UDF.equals(labelSelectItem)) {
                new SwingWorker<>() {
                    @Override
                    protected Object doInBackground() { Thread.currentThread().setName("SwingWorkerExploit");
                        ActionExploit.this.start(null, null, null);
                        return null;
                    }
                }.execute();
                return;
            }
            if (
                ManagerExploit.EXPLOIT_SQL.equals(labelSelectItem)
                && (ManagerExploit.this.password.get().getText().isEmpty() || ManagerExploit.this.username.get().getText().isEmpty())
            ) {
                LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Missing credentials (tips: search and read file containing hardcoded credentials)");
                return;
            }
            if (ManagerExploit.this.listPaths.getSelectedValuesList().isEmpty()) {
                LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Select at least one directory in the list");
                return;
            }

            String urlShell = this.validateAndNormalizeUrlShell();
            if (urlShell == null) {
                return;
            }
            AtomicReference<File> fileToUpload = new AtomicReference<>();
            if (this.validateFileSelection(labelSelectItem, fileToUpload)) {
                return;
            }

            new SwingWorker<>() {
                @Override
                protected Object doInBackground() { Thread.currentThread().setName("SwingWorkerExploitNonUdf");
                    ManagerExploit.this.horizontalGlue.setVisible(false);
                    ManagerExploit.this.progressBar.setVisible(true);
                    ManagerExploit.this.listPaths.getSelectedValuesList().forEach(pathExploit -> {
                        LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, String.format("Checking path [%s]...", pathExploit));
                        ActionExploit.this.start(pathExploit.toString(), urlShell, fileToUpload.get());
                    });
                    ManagerExploit.this.endProcess();
                    return null;
                }
            }.execute();
        }

        private boolean validateFileSelection(String labelSelectItem, AtomicReference<File> fileToUpload) {
            if (ManagerExploit.EXPLOIT_UPLOAD.equals(labelSelectItem)) {
                fileToUpload.set(ManagerExploit.chooseFile());
                if (fileToUpload.get() == null) {
                    LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Missing file, please select a file");
                    return true;
                }
            }
            return false;
        }

        private String validateAndNormalizeUrlShell() {
            String urlShell = ManagerExploit.this.textfieldUrlShell.getText();
            if (!urlShell.isEmpty() && !urlShell.matches("(?i)^https?://.*")) {
                if (!urlShell.matches("(?i)^\\w+://.*")) {
                    LOGGER.log(LogLevelUtil.CONSOLE_INFORM, "Undefined shell URL protocol, forcing to [https://]");
                    urlShell = "https://"+ urlShell;
                } else {
                    LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Unknown URL protocol");
                    return null;
                }
            }
            if (StringUtils.isNotEmpty(urlShell)) {
                try {
                    new URI(urlShell);
                } catch (URISyntaxException e) {
                    LOGGER.log(LogLevelUtil.CONSOLE_ERROR, String.format("Incorrect URL: %s", e.getMessage()));
                    return null;
                }
            }
            return urlShell;
        }

        private void start(String pathExploit, String urlShellFinal, File fileToUpload) {
            try {
                ManagerExploit.this.createPayload(pathExploit, urlShellFinal, fileToUpload);
            } catch (JSqlException | IllegalArgumentException e) {
                LOGGER.log(LogLevelUtil.CONSOLE_ERROR, String.format("Payload creation failure: %s", e.getMessage()));
            }
        }
    }

    private static boolean isValid(String labelSelectItem) {
        return
            ManagerExploit.EXPLOIT_UDF.equals(labelSelectItem) && Arrays.asList(
                MediatorHelper.model().getMediatorVendor().getSqlite(),
                MediatorHelper.model().getMediatorVendor().getMysql(),
                MediatorHelper.model().getMediatorVendor().getPostgres(),
                MediatorHelper.model().getMediatorVendor().getH2()
            ).contains(MediatorHelper.model().getMediatorVendor().getVendor())
            || Arrays.asList(ManagerExploit.EXPLOIT_WEB, ManagerExploit.EXPLOIT_UPLOAD).contains(labelSelectItem) && Arrays.asList(
                MediatorHelper.model().getMediatorVendor().getDerby(),
                MediatorHelper.model().getMediatorVendor().getHsqldb(),
                MediatorHelper.model().getMediatorVendor().getH2(),
                MediatorHelper.model().getMediatorVendor().getSqlite(),
                MediatorHelper.model().getMediatorVendor().getMysql(),
                MediatorHelper.model().getMediatorVendor().getPostgres()
            ).contains(MediatorHelper.model().getMediatorVendor().getVendor())
            || Arrays.asList(ManagerExploit.EXPLOIT_SQL).contains(labelSelectItem) && Arrays.asList(
                MediatorHelper.model().getMediatorVendor().getMysql(),
                MediatorHelper.model().getMediatorVendor().getPostgres()
            ).contains(MediatorHelper.model().getMediatorVendor().getVendor());
    }

    private static File chooseFile() {
        var filechooser = new JFileChooser(MediatorHelper.model().getMediatorUtils().getPreferencesUtil().getPathFile());
        filechooser.setDialogTitle(I18nUtil.valueByKey("UPLOAD_DIALOG_TEXT"));
        int returnVal = filechooser.showOpenDialog(MediatorHelper.frame());
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            return filechooser.getSelectedFile();
        }
        return null;
    }

    protected void createPayload(String pathExploit, String urlShell, File fileToUpload) throws JSqlException {
        var exploitMethod = ExploitMode.forName(
            Objects.requireNonNull(this.comboBoxExploitModes.getSelectedItem()).toString()
        ).orElse(ExploitMode.AUTO);

        if (pathExploit != null && !pathExploit.endsWith("/")) {
            pathExploit += "/";
        }
        String pathNetshare = this.netshare.get().getText();
        if (exploitMethod == ExploitMode.NETSHARE && !pathNetshare.endsWith("\\")) {
            pathNetshare += "\\";
        }

        var modelItemType = (ModelItemType) Objects.requireNonNull(this.comboBoxExploitTypes.getSelectedItem());
        var keyLabel = modelItemType.getKeyLabel();
        var vendor = MediatorHelper.model().getMediatorVendor().getVendor();

        if (ManagerExploit.EXPLOIT_UDF.equals(keyLabel)) {
            ManagerExploit.handleUdfExploit(vendor, pathNetshare, exploitMethod);
        } else if (ManagerExploit.EXPLOIT_WEB.equals(keyLabel)) {
            ManagerExploit.handleWebExploit(pathExploit, urlShell, vendor, pathNetshare, exploitMethod);
        } else if (ManagerExploit.EXPLOIT_SQL.equals(keyLabel)) {
            this.handleSqlExploit(pathExploit, urlShell, vendor, pathNetshare, exploitMethod);
        } else if (ManagerExploit.EXPLOIT_UPLOAD.equals(keyLabel)) {
            ManagerExploit.handleUploadExploit(pathExploit, urlShell, fileToUpload, vendor, pathNetshare, exploitMethod);
        }
    }

    private static void handleUdfExploit(Vendor vendor, String pathNetshare, ExploitMode exploitMethod) throws JSqlException {
        if (vendor == MediatorHelper.model().getMediatorVendor().getMysql()) {
            MediatorHelper.model().getResourceAccess().getExploitMysql().createUdf(pathNetshare, exploitMethod);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getPostgres()) {
            MediatorHelper.model().getResourceAccess().getExploitPostgres().createUdf(null);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getSqlite()) {
            MediatorHelper.model().getResourceAccess().getExploitSqlite().createUdf();
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getH2()) {
            MediatorHelper.model().getResourceAccess().getExploitH2().createUdf();
        }
    }

    private static void handleWebExploit(String pathExploit, String urlShell, Vendor vendor, String pathNetshare, ExploitMode exploitMethod) throws JSqlException {
        if (vendor == MediatorHelper.model().getMediatorVendor().getMysql()) {
            MediatorHelper.model().getResourceAccess().getExploitMysql().createWeb(pathExploit, urlShell, pathNetshare, exploitMethod);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getPostgres()) {
            MediatorHelper.model().getResourceAccess().getExploitPostgres().createWeb(pathExploit, urlShell);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getSqlite()) {
            MediatorHelper.model().getResourceAccess().getExploitSqlite().createWeb(pathExploit, urlShell);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getHsqldb()) {
            MediatorHelper.model().getResourceAccess().getExploitHsqldb().createWeb(pathExploit, urlShell);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getH2()) {
            MediatorHelper.model().getResourceAccess().getExploitH2().createWeb(pathExploit, urlShell);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getDerby()) {
            MediatorHelper.model().getResourceAccess().getExploitDerby().createWeb(pathExploit, urlShell);
        }
    }

    private void handleSqlExploit(String pathExploit, String urlShell, Vendor vendor, String pathNetshare, ExploitMode exploitMethod) throws JSqlException {
        String login = this.username.get().getText();
        String pass = this.password.get().getText();
        if (vendor == MediatorHelper.model().getMediatorVendor().getMysql()) {
            MediatorHelper.model().getResourceAccess().getExploitMysql().createSql(pathExploit, urlShell, pathNetshare, exploitMethod, login, pass);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getPostgres()) {
            MediatorHelper.model().getResourceAccess().getExploitPostgres().createSql(pathExploit, urlShell, login, pass);
        }
    }

    private static void handleUploadExploit(String pathExploit, String urlShell, File fileToUpload, Vendor vendor, String pathNetshare, ExploitMode exploitMethod) throws JSqlException {
        if (vendor == MediatorHelper.model().getMediatorVendor().getMysql()) {
            MediatorHelper.model().getResourceAccess().getExploitMysql().createUpload(pathExploit, urlShell, pathNetshare, exploitMethod, fileToUpload);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getPostgres()) {
            MediatorHelper.model().getResourceAccess().getExploitPostgres().createUpload(pathExploit, urlShell, fileToUpload);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getSqlite()) {
            MediatorHelper.model().getResourceAccess().getExploitSqlite().createUpload(pathExploit, urlShell, fileToUpload);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getHsqldb()) {
            MediatorHelper.model().getResourceAccess().getExploitHsqldb().createUpload(pathExploit, urlShell, fileToUpload);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getH2()) {
            MediatorHelper.model().getResourceAccess().getExploitH2().createUpload(pathExploit, urlShell, fileToUpload);
        } else if (vendor == MediatorHelper.model().getMediatorVendor().getDerby()) {
            MediatorHelper.model().getResourceAccess().getExploitDerby().createUpload(pathExploit, urlShell, fileToUpload);
        }
    }
}
